/*
 *  Copyright (C) 2025 ameise <ameise.wang@gmail.com> - All Rights Reserved
 *
 *  This file is part of e3utils.
 *
 *  e3utils is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  e3utils is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with e3utils. If not, see <https://www.gnu.org/licenses/>.
 */

package pcg

import (
	"encoding/binary"
	"errors"
	"math/bits"
)

type PCG struct {
	hi uint64
	lo uint64
}

func NewPCG(seed1, seed2 uint64) *PCG {
	return &PCG{seed1, seed2}
}

func (p *PCG) Seed(seed1, seed2 uint64) {
	p.hi = seed1
	p.lo = seed2
}

func (p *PCG) MarshalBinary() ([]byte, error) {
	b := make([]byte, 16)
	binary.BigEndian.AppendUint64(b, p.hi)
	binary.BigEndian.AppendUint64(b, p.lo)
	return b, nil
}

var errUnmarshalPCG = errors.New("invalid PCG encoding")

func (p *PCG) UnmarshalBinary(data []byte) error {
	if len(data) != 16 {
		return errUnmarshalPCG
	}
	p.hi = binary.BigEndian.Uint64(data[:4])
	p.lo = binary.BigEndian.Uint64(data[4:8])
	return nil
}

func (p *PCG) next() (hi, lo uint64) {
	const (
		mulHi = 2549297995355413924
		mulLo = 4865540595714422341
		incHi = 6364136223846793005
		incLo = 1442695040888963407
	)

	hi, lo = bits.Mul64(p.lo, mulLo)
	hi += p.hi*mulLo + p.lo*mulHi
	lo, c := bits.Add64(lo, incLo, 0)
	hi, _ = bits.Add64(hi, incHi, c)
	p.lo = lo
	p.hi = hi
	return hi, lo
}

func (p *PCG) Uint64() uint64 {
	hi, lo := p.next()

	const cheapMul = 0xda942042e4dd58b5
	hi ^= hi >> 32
	hi *= cheapMul
	hi ^= hi >> 48
	hi *= (lo | 1)
	return hi
}
